package iiris

import (
	"fmt"
	"github.com/kataras/golog"
	"github.com/kataras/pio"
	"path"
	"runtime"
	"strings"
	"sync"
)

func resetIrisGologLevel() {
	golog.Levels = map[golog.Level]*golog.LevelMetadata{
		golog.DisableLevel: {
			Name:             "disable",
			AlternativeNames: []string{"disabled"},
			Title:            "",
		},
		golog.FatalLevel: {
			Name:      "fatal",
			Title:     "[FATAL]",
			ColorCode: pio.Red,
			Style:     []pio.RichOption{pio.Background},
		},
		golog.ErrorLevel: {
			Name:      "error",
			Title:     "[ERROR]",
			ColorCode: pio.Red,
		},
		golog.WarnLevel: {
			Name:             "warn",
			AlternativeNames: []string{"warning"},
			Title:            "[WARN]",
			ColorCode:        pio.Magenta,
		},
		golog.InfoLevel: {
			Name:      "info",
			Title:     "[INFO]",
			ColorCode: pio.Green,
		},
		golog.DebugLevel: {
			Name:      "debug",
			Title:     "[DEBUG]",
			ColorCode: pio.Blue,
		},
	}

}

type GologHijacker struct {
	//启用文件名称log
	EnableFileNameLog bool
	//启用函数名称log
	EnableFuncNameLog bool
	//启用短函数名称
	EnableShortFuncName bool
	Skip                int
}

func NewGologHijacker() *GologHijacker {
	return &GologHijacker{
		EnableFileNameLog:   true,
		EnableFuncNameLog:   true,
		EnableShortFuncName: true,
		Skip:                8,
	}
}

func (hook *GologHijacker) Fire(ctx *pio.Ctx) {
	//buf:=make([]byte,0,1024)
	//runtime.Stack(buf,true)
	//fmt.Println(string(buf))
	l, ok := ctx.Value.(*golog.Log)
	if !ok {
		ctx.Next()
		return
	}
	line := ""
	if t := l.FormatTime(); t != "" {
		line += pio.Rich("["+t+"]", pio.Magenta)

	}
	level := golog.GetTextForLevel(l.Level, false)
	if level != "" {
		level = level[1 : len(level)-1]
		if ctx.Printer.IsTerminal {
			line += pio.Rich(" "+level+" ", golog.Levels[l.Level].ColorCode)
		} else {
			line += level + " "
		}

	}
	if hook.EnableFileNameLog || hook.EnableFuncNameLog {
		file, function, codeLine := hook.findCaller(hook.Skip)

		if hook.EnableShortFuncName {
			_, function = path.Split(function)
		}

		if hook.EnableFileNameLog && hook.EnableFuncNameLog {
			line += pio.Rich(fmt.Sprintf("%s(%s:%d) ", function, file, codeLine), pio.Cyan)
		}
		if hook.EnableFileNameLog && !hook.EnableFuncNameLog {
			line += pio.Rich(fmt.Sprintf("%s(%d) ", file, codeLine), pio.Cyan)
		}
		if !hook.EnableFileNameLog && hook.EnableFuncNameLog {
			line += pio.Rich(fmt.Sprintf("%s(%d) ", function, codeLine), pio.Cyan)
		}
	}
	line += l.Message
	//
	var b []byte

	if prefix := l.Logger.Prefix; len(prefix) > 0 {
		b = append([]byte(prefix), []byte(line)...)
	} else {
		b = []byte(line)
	}

	//if pref := l.Logger.Prefix; len(pref) > 0 {
	//	b = append(pref, []byte(line)...)
	//} else {
	//	b = []byte(line)
	//}

	ctx.Store(b, nil)
	ctx.Next()
}
func (hook *GologHijacker) findCaller2(skip int) (string, string, int) {
	var (
		file     string
		function string
		line     int
	)
	frame := getCaller()

	if frame != nil {
		line = frame.Line
		function = frame.Function
		if hook.EnableShortFuncName {
			_, function = path.Split(function)
		}
		dir, filename := path.Split(frame.File)
		f := path.Base(dir)
		file = fmt.Sprintf("%s/%s", f, filename)
	}
	return file, function, line
}

func (hook *GologHijacker) findCaller(skip int) (string, string, int) {
	var (
		pc       uintptr
		file     string
		function string
		line     int
	)
	for i := 0; i < 10; i++ {
		pc, file, line = hook.getCaller(skip + i)
		if !strings.HasPrefix(file, "kataras/") {
			break
		}
	}
	if pc != 0 && hook.EnableFuncNameLog {
		frames := runtime.CallersFrames([]uintptr{pc})
		frame, _ := frames.Next()
		function = frame.Function
	}
	if hook.EnableShortFuncName {
		_, function = path.Split(function)
	}

	return file, function, line
}

func (hook *GologHijacker) getCaller(skip int) (uintptr, string, int) {
	pc, file, line, ok := runtime.Caller(skip)

	if !ok {
		return 0, "", 0
	}

	n := 0
	for i := len(file) - 1; i > 0; i-- {
		if file[i] == '/' {
			n += 1
			if n >= 3 {
				file = file[i+1:]
				break
			}
		}
	}

	return pc, file, line
}

var (
	bufferPool *sync.Pool

	// qualified package name, cached at first use
	logrusPackage string

	// Positions in the call stack when tracing to report the calling method
	minimumCallerDepth int

	// Used for caller information initialisation
	callerInitOnce sync.Once
)

const (
	maximumCallerDepth int = 30
	knownLogrusFrames  int = 4
)

// getCaller retrieves the name of the first non-logrus calling function
func getCaller() *runtime.Frame {

	// cache this package's fully-qualified name
	callerInitOnce.Do(func() {
		//pcs := make([]uintptr, 2)
		//_ = runtime.Callers(0, pcs)
		//fmt.Println(runtime.FuncForPC(pcs[0]).Name())
		//fmt.Println(runtime.FuncForPC(pcs[1]).Name())
		//logrusPackage = getPackageName(runtime.FuncForPC(pcs[0]).Name())
		logrusPackage = "github.com/kataras/pio"
		// now that we have the cache, we can skip a minimum count of known-logrus functions
		// XXX this is dubious, the number of frames may vary
		minimumCallerDepth = knownLogrusFrames
	})

	// Restrict the lookback frames to avoid runaway lookups
	pcs := make([]uintptr, maximumCallerDepth)
	depth := runtime.Callers(minimumCallerDepth, pcs)
	frames := runtime.CallersFrames(pcs[:depth])
	for f, again := frames.Next(); again; f, again = frames.Next() {
		pkg := getPackageName(f.Function)

		// If the caller isn't part of this package, we're done
		//if pkg != logrusPackage  {
		if !strings.Contains(pkg, "github.com/kataras/") {
			return &f
		}
	}

	// if we got here, we failed to find the caller's context
	return nil
}

// getPackageName reduces a fully qualified function name to the package name
// There really ought to be to be a better way...
func getPackageName(f string) string {
	for {
		lastPeriod := strings.LastIndex(f, ".")
		lastSlash := strings.LastIndex(f, "/")
		if lastPeriod > lastSlash {
			f = f[:lastPeriod]
		} else {
			break
		}
	}

	return f
}
